import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import gsap from 'gsap'
import * as dat from 'dat.gui' // 导入gui
/*
* 第一步新建一个场景
* 第二步创建相机
* 添加物体
* 渲染器
* 渲染render
* */
// AxisHelper
export default class ThreeJs {
    public scene: THREE.Scene | null = null
    public canvas: HTMLElement
    public camera:THREE.PerspectiveCamera | null = null
    public renderer: THREE.WebGLRenderer | null = null
    public mesh: THREE.Mesh | null = null
    public mesh1: THREE.Mesh | null = null
    public controls: OrbitControls | null = null // 轨道控制器
    public gui: dat.GUI | null = null
    public loadingMessage : THREE.LoadingManager | undefined = undefined

    constructor () {
      this.canvas = document.getElementById('canvas') as HTMLElement
      this.init()
    }

    init ():void {
      this.scene = new THREE.Scene() // 第一步新建一个场景
      this.gui = new dat.GUI()
      this.createCameraSetting()// 第二步创建相机
      this.getProgress()
      this.createSphere()
      // this.createGeometry() // 添加物体
      // this.createSelfGeometry()
      this.setRenderer()// 渲染器
      this.setOrbitControls()
      this.setAxesHelper()
      this.animate()// 渲染
      this.windowSelfAdaption()
      this.setGui()
      this.setLightSetting()
    }

    /**
     * 相机设置
     */
    createCameraSetting ():void {
      this.camera = new THREE.PerspectiveCamera(75, this.canvas.offsetWidth / this.canvas.offsetHeight, 0.1, 10000)
      this.camera.position.set(-5, 2, 10)
      this.scene?.add(this.camera)
    }

    /*
    * 创建物体(立方几何体)
    */
    createGeometry ():void {
      const geometry = new THREE.BoxGeometry(1, 1, 1, 100, 100)
      // const material = new THREE.MeshBasicMaterial({ // 添加材质
      //   color: 0xff0000
      // })

      const textureLoader = new THREE.TextureLoader(this.loadingMessage)
      const doorColorTexture = textureLoader.load('src/assets/door/color.jpg')
      const doorAlphaTexture = textureLoader.load('src/assets/door/alpha.jpg') // 透明纹理 alphaMap && transparent 一起使用
      const doorAoTexture = textureLoader.load('src/assets/door/ambientOcclusion.jpg')// 深度
      const doorHeightTexture = textureLoader.load('src/assets/door/height.jpg') // 凹凸
      const doorRoughnessTexture = textureLoader.load('src/assets/door/roughness.jpg')// 粗糙度贴图
      const doorMetalnessTexture = textureLoader.load('src/assets/door/metalness.jpg')// 金属
      const doorNormalTexture = textureLoader.load('src/assets/door/normal.jpg')// 法线贴图
      doorColorTexture.magFilter = THREE.NearestFilter
      doorColorTexture.minFilter = THREE.NearestFilter

      const material = new THREE.MeshStandardMaterial({
        color: '#ffff00',
        map: doorColorTexture,
        side: THREE.DoubleSide,
        alphaMap: doorAlphaTexture,
        transparent: true,
        aoMap: doorAoTexture,
        aoMapIntensity: 0.5,
        displacementMap: doorHeightTexture,
        displacementScale: 0.05,
        roughnessMap: doorRoughnessTexture,
        roughness: 1,
        metalness: 1,
        metalnessMap: doorMetalnessTexture,
        normalMap: doorNormalTexture
      })

      this.mesh = new THREE.Mesh(geometry, material)
      this.scene?.add(this.mesh)
      geometry.setAttribute('uv2', new THREE.BufferAttribute(geometry.attributes.uv.array, 2))
    }

    createSphere ():void {
      const geometry = new THREE.SphereBufferGeometry(1, 20, 20)
      const cubeTextureLoader = new THREE.CubeTextureLoader()
      const envMapTexture = cubeTextureLoader.load([
        'src/assets/1/px.jpg',
        'src/assets/1/nx.jpg',
        'src/assets/1/py.jpg',
        'src/assets/1/ny.jpg',
        'src/assets/1/pz.jpg',
        'src/assets/1/nz.jpg'
      ])

      const material = new THREE.MeshStandardMaterial({
        metalness: 0.7,
        roughness: 0.01,
        envMap: envMapTexture
      })
      this.mesh1 = new THREE.Mesh(geometry, material)
      this.scene?.add(this.mesh1)
      if (this.scene) this.scene.background = envMapTexture
    }

    /*
    * 纹理加载进度
    * */
    getProgress ():void {
      this.loadingMessage = new THREE.LoadingManager()
      this.loadingMessage.onStart = function (url, itemsLoaded, itemsTotal) {
        console.log('Started loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.')
      }

      this.loadingMessage.onLoad = function () {
        console.log('Loading complete!')
      }

      this.loadingMessage.onProgress = function (url, itemsLoaded, itemsTotal) {
        console.log('Loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.')
      }

      this.loadingMessage.onError = function (url) {
        console.log('There was an error loading ' + url)
      }
    }

    createSelfGeometry ():void {
      const geometry = new THREE.PlaneGeometry(1, 1, 200, 200)

      const textureLoader = new THREE.TextureLoader()
      const doorColorTexture = textureLoader.load('src/assets/door/color.jpg')
      const doorHeightTexture = textureLoader.load('src/assets/door/height.jpg')
      // 创建一个简单的矩形. 在这里我们左上和右下顶点被复制了两次。
      // itemSize = 3 因为每个顶点都是一个三元组。
      const material = new THREE.MeshStandardMaterial({
        map: doorColorTexture,
        side: THREE.DoubleSide,
        displacementMap: doorHeightTexture,
        displacementScale: 0.05
      })
      const mesh = new THREE.Mesh(geometry, material)
      mesh.position.set(2, 0, 0)
      // 平面设置第二组uv
      this.scene?.add(mesh)
    }

    /*
    * 创建轨道控制器(鼠标操控)
    */
    setOrbitControls ():void {
      if (this.camera && this.renderer) {
        this.controls = new OrbitControls(this.camera, this.renderer?.domElement)
        this.controls.enableDamping = true // 设置控制器阻尼 让其更有惯性真实性
      }
    }

    // 设置三维辅助坐标系
    setAxesHelper ():void {
      const axisHelper = new THREE.AxesHelper(5)
      this.scene?.add(axisHelper)
    }

    /**
     * 光源设置
     */
    setLightSetting ():void {
      const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
      const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5)
      // 设置光源的方向：通过光源position属性和目标指向对象的position属性计算
      directionalLight.position.set(10, 10, 10)
      // 方向光指向对象网格模型mesh2，可以不设置，默认的位置是0,0,0
      this.scene?.add(ambientLight)
      this.scene?.add(directionalLight)
    }

    // 设置渲染器
    setRenderer ():void {
      this.renderer = new THREE.WebGLRenderer()
      this.renderer.setSize(this.canvas.offsetWidth, this.canvas.offsetHeight)// 设置渲染区域尺寸
      this.renderer.setClearColor(0x000000, 1) // 设置背景颜色
      this.canvas.appendChild(this.renderer.domElement) // body元素中插入canvas对象
    }

    /**
     * 创建渲染器对象
     */
    render ():void {
      if (this.scene && this.camera) this.renderer?.render(this.scene, this.camera)// 执行渲染操作
    }

    // 动画
    animate ():void {
      this.controls?.update()
      // this.mesh?.rotateX(0.01)// 每次绕y轴旋转0.01弧度
      // this.mesh?.rotateY(0.01)// 每次绕y轴旋转0.01弧度
      requestAnimationFrame(this.animate.bind(this))
      this.render()
    }

    /*
    * 根据屏幕尺寸变化实现自适应画面
    */
    windowSelfAdaption ():void {
      window.addEventListener('resize', () => {
        if (this.camera) {
          this.camera.aspect = this.canvas.offsetWidth / this.canvas.offsetHeight
          this.camera.updateProjectionMatrix()// 更新矩阵
          this.renderer?.setSize(this.canvas.offsetWidth, this.canvas.offsetHeight) // 更新渲染器
          this.renderer?.setPixelRatio(window.devicePixelRatio)// 设置渲染器像素比
        }
      })
    }

    /*
    *设置gui
    */
    setGui ():void {
      const params = {
        color: '#ffffff',
        motion: () => { // 物体运动
          if (this.mesh) {
            gsap.to(this.mesh.position, {
              x: 5,
              duration: 2,
              repeat: -1,
              yoyo: true,
              ease: 'power2.out'
            })
          }
        }
      }
      if (this.mesh) {
        const folder = this.gui?.addFolder('物体控制')
        folder?.add(this.mesh?.position, 'x').min(0).max(5).name('物体x轴坐标')
        folder?.addColor(params, 'color').onChange((value) => {
          // @ts-ignore
          this.mesh?.material.color.set(value)
        }).name('物体颜色')
        // 是否显示
        folder?.add(this.mesh, 'visible').name('是否显示')
        folder?.add(this.mesh.material, 'wireframe').name('线条显示')
        folder?.add(params, 'motion').name('物体运动')
      }
    }
}
